#pragma once

#include <sstream>

#include "data.hpp"

#include "httplib.h"


extern Cloud::DataManager *_data;

namespace Cloud{
class Service
{
public:
    Service(); // 构造函数 - 成员变量初始化
    bool RunModule(); // 服务器运行 - 业务处理
private:
    int _server_port; // 服务器端口号
    std::string _server_ip; // 服务器IP
    std::string _download_prefix; // 下载前缀
    httplib::Server _server; // 服务器实例
private:
    static void Upload(const httplib::Request &req, httplib::Response &rsp); // 上传请求业务处理函数
    static void Listshow(const httplib::Request &req, httplib::Response &rsp); // 页面展示请求业务处理函数
    static void Download(const httplib::Request &req, httplib::Response &rsp); // 下载请求业务处理函数
                                                                               
    static std::string TimeToStr(time_t t); // 时间戳转化为时间
                                            
    static std::string GetETag(const BackupInfo&);
}; // class Service

Service::Service(){
    Config *config = Config::GetInstance();
    _server_port = config->GetServerPort();
    _server_ip = config->GetServerIP();
    _download_prefix = config->GetDownloadPrefix();
}

bool Service::RunModule(){
    _server.Post("/upload",Upload);
    _server.Get("/listshow",Listshow);
    _server.Get("/",Listshow);

    
    std::string download_url = _download_prefix + "(.*)";
                                // 正则表达式中 .点 表示 匹配除换行符 \n 之外的任何单字符
                                // * 表示 匹配前面的子表达式零次或多次
                                // 通过正则表达式获取客户端发送请求的需要下载的文件
    _server.Get(download_url,Download);
                                // 采用 string 对象的方式确保 _download_prefix 的可自定义性
    std::cout << _server_ip << " : " << _server_port<<std::endl;
    bool ret  = _server.listen(_server_ip,_server_port);
    if(!ret){
        std::cout<<"Server RunModule listen error"<<std::endl;
        return false;
    }
    return true;
}

std::string Service::TimeToStr(time_t t){
    return std::ctime(&t);
}

void Service::Upload(const httplib::Request &req, httplib::Response &rsp){
    std::cout<<"Debug to Upload"<<std::endl;

    auto ret = req.has_file("file"); // 表单中的所上传文件字段也得是file  这个file 不表示文件名就是file  是两码事
    
    if(ret == false){ // 表示不存在字段 "file" 的文件
        rsp.status = 400; // 返回状态码 400
        return;
    }
    const auto& file = req.get_file_value("file"); // 获取字段名为 "file" 的文件
                                                   // 具体是需要文件名以及文件内容

    // 将文件写入至对应位置 (使用 FileUtil 打开文件 并写入文件信息)
    std::string back_dir = Config::GetInstance()->GetBackDir(); // 从配置信息内读取备份路径
                                                                
    std::string realpath = back_dir + FileUtil(file.filename).FileName(); // 备份路径 + 真实文件名 获取 需要打开上传的文件路径
    // std::string realpath = back_dir + file.filename; // 备份路径 + 真实文件名 获取 需要打开上传的文件路径

    // std::cout<<"To Debug: "<<realpath<<std::endl;

    FileUtil fu(realpath); // 用文件工具包传入路径打开文件 
    // std::cout<<"To Debug: SetContent Before"<<std::endl;
    fu.SetContent(file.content); // 将文件写入
    // std::cout<<"To Debug: SetContent After"<<std::endl;
    BackupInfo info; // 实例化一个信息对象
    // std::cout<<"service.hpp To Debug: NewBackupInfo before"<<std::endl;
    info.NewBackupInfo(realpath); // 调用 NewBackupInfo 将会自动填充信息
    // std::cout<<"service.hpp To Debug: Insert before"<<std::endl;
    _data->Insert(info); // 将信息插入
    // std::cout<<"service.hpp To Debug: Insert after"<<std::endl;
    return;
}

void Service::Listshow(const httplib::Request &req, httplib::Response &rsp){
    // 获取所有文件的信息
    std::vector<BackupInfo> infos;
    _data->GetAll(&infos);

    // 组织前端信息
    std::stringstream ss;
    ss<<"<html><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Download</title></head><body><h1>Download</h1><table>";
    for(auto &it : infos){
        ss<<"<tr>";
        std::string filename = FileUtil(it.real_path).FileName();
        ss<<"<td><a href='" << it.url <<"'>"<<filename<<"</a></td>"; 
        ss<<"<td align='right'>" << TimeToStr(it.atime)<<"</td>" ;
        ss<<"<td align='right'>" << it.fsize << "k</td>";
        ss<<"</tr>";
    }
    ss<<"</table></body></html>";
    rsp.set_header("Content-Type","text/html");
    rsp.status = 200;
    rsp.body.clear();
    rsp.body = ss.str();
    return;
} 
    


std::string Service::GetETag(const BackupInfo& info){
    // ETag 字段为 文件名-文件大小-最后修改时间
    FileUtil fu(info.real_path);
    std::string etag = fu.FileName();
    etag += "-";
    etag += std::to_string(info.fsize);
    etag += "-";
    etag += std::to_string(info.mtime);
    return etag;
}


// 下载功能 (未对断点续传功能进行解释 但仍具有断点续传功能)
/*
 *  该版本也可以用 同样支持断点续传 原因是httplib中已经实现了断点续传相关操作
void Service::Download(const httplib::Request &req, httplib::Response &rsp){
    
     //1. 获取客户端请求的资源路径 path , 即 Request::path
     //2. 根据路径获取文件备份信息 BackupInfo
     //3. 判断文件是否被压缩 若是压缩则解压
     //     3-1. 删除压缩包 修改备份信息 ( BackupInfo中pack_falg 标志位进行修改 )
     //4. 读取文件数据将数据放置 Response::body 中
     //5. 设置响应头部字段: ETag, Accept-Ranges: bytes
    
     

    // 获取请求的 url
    std::string url = req.path;

    // 获取 BackupInfo 数据
    BackupInfo info;
    _data->GetOneByURL(url,&info);
    if(info.pack_flag){
        // 为压缩包 - 需要进行解压
        FileUtil packfu(url);
        // std::string packname = Config::GetInstance()->GetBackDir() + fu.FileName();
        
        // 解压缩
        packfu.UnCompress(info.real_path);
        // 删除压缩文件
        packfu.Remove();

        // 修改备份信息 并更新
        info.pack_flag = false;
        _data->Update(info);
    }
    // 填充响应正文内容
    FileUtil fu(info.real_path);
    fu.GetContent(&rsp.body);

    // 设置响应头部字段
    rsp.set_header("Accept-Ranges","bytes"); // 告诉客户端服务端支持断点续传功能
    rsp.set_header("ETag",GetETag(info));
    
    // Content-Type 头部字段十分重要 
    // 未设置 Content-Type 字段将会将内容直接进行展示而不是下载
    // application/octet-stream 表示二进制流 通常用于下载文件
    rsp.set_header("Content-Type","application/octet-stream"); // 

    rsp.status = 200;
    return;
}
*/

// 下载功能 + 断点续传功能解释
void Service::Download(const httplib::Request &req, httplib::Response &rsp){
    // 获取请求的 url
    std::string url = req.path;

    // 获取 BackupInfo 数据
    BackupInfo info;
    _data->GetOneByURL(url,&info);
    if(info.pack_flag){
        std::cout<<"Debug1-> UnCompress pack_flag:"<<info.pack_flag<<std::endl;
        // 为压缩包 - 需要进行解压

        // FileUtil packfu(url); // BUG-DONE
        FileUtil packfu(info.pack_path);


        // std::string packname = Config::GetInstance()->GetBackDir() + fu.FileName();
        
        std::cout<<"Debug2-> UnCompress pack_flag:"<<info.pack_flag<<std::endl;

        
        std::cout<<"info.real_path ------ :"<<info.real_path<<std::endl;
        // 解压缩
        std::cout<<packfu.FileName()<<std::endl;
        std::cout<<"FileName() After"<<std::endl;
                    // TODO - Bug!!!!!!
       // std::cout<<packfu.FileSize()<<std::endl;
       // std::cout<<"FileSize() After"<<std::endl;
       // std::cout<<packfu.LastATime()<<std::endl;
       // std::cout<<packfu.LastMTime()<<std::endl;
        
        
        packfu.UnCompress(info.real_path);
        std::cout<<"Debug3-> UnCompress pack_flag:"<<info.pack_flag<<std::endl;
        // 删除压缩文件
        packfu.Remove();

        std::cout<<"Debug4-> UnCompress pack_flag:"<<info.pack_flag<<std::endl;
        // 修改备份信息 并更新
        info.pack_flag = false;
        _data->Update(info);
    }
    
    // 填充响应正文内容
    FileUtil fu(info.real_path);

    bool breakpflag = false;
    std::string old_etag;
    if(req.has_header("If-Range")){ // 如果所接收的请求中有 If-Range 字段则表示是断点续传
        old_etag = req.get_header_value("If-Range");
        if(old_etag == GetETag(info)){
            breakpflag = true; // 表示为断点续传
        }
    }

    if(breakpflag == false){
        fu.GetContent(&rsp.body);
        // 设置响应头部字段
        rsp.set_header("Accept-Ranges","bytes"); // 告诉客户端服务端支持断点续传功能
        rsp.set_header("ETag",GetETag(info));

        rsp.set_header("Content-Type","application/octet-stream"); // 二进制的形式传输文件 

        rsp.status = 200;
        return;
    }
    else{
        // 此处表示进行断点续传
        // 有一点为实际上断点续传的功能已经在 httplib 库中实现 因此实际在使用 httplib 实现断点续传功能时只需要
        // 在第一次客户端发起的下载请求对应的响应中返回 "Accept-Ranges" 并将所有的数据返回给 httplib::Response::body 即可
        
        // 断点续传的思路为
        // 解析 客户端所发来的请求的 "Range" 字段, 通常 Range 字段中包含了客户端请求所需数据的区间范围 "bytes=start-end" 的形式
        // 解析对应的 Range 字段为区间, 将对应区间的数据返回给 httplib::Response::body
        // 而后需要设置头部 "Content-Range" 字段 ,返回给客户端本次返回的数据范围 "bytes start-end/fsize"
        // 断点续传所返回的状态码为 206 表示服务端已经处理部分客户端请求 区别于 200
        
        rsp.set_header("Content-Type","application/octet-stream"); // 二进制的形式传输文件
        rsp.set_header("Accept-Ranges","bytes"); // 告诉客户端服务端支持断点续传功能
        rsp.set_header("ETag",GetETag(info));
        fu.GetContent(&rsp.body);
        rsp.status = 206;
    }

}


} // namespace Cloud
